/*
    PhotoFlip, a free 3d photo managment suite.
    Copyright (C) 2007  Elisée MAURER, Gaétan WALTER
    
    This file is part of PhotoFlip.
    Contact: http://www.photoflip.org/

    PhotoFlip is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    PhotoFlip is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with PhotoFlip.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "glarea.hpp"
#include "constants.hpp"

#include <glib/gstdio.h>
#include <GL/glu.h>
#include <iostream>
#include <cmath>
#include <list>

#include <librsvg/rsvg.h>

#include "md5.hpp"

GLArea::GLArea(library *lib): m_lib(lib), m_visu(NULL), m_dock(m_lib) {
	Glib::RefPtr<Gdk::GL::Config> glconfig =
		Gdk::GL::Config::create(Gdk::GL::MODE_RGB | Gdk::GL::MODE_DEPTH |
		Gdk::GL::MODE_STENCIL | Gdk::GL::MODE_DOUBLE);
	
	if(!glconfig) {
		glconfig = Gdk::GL::Config::create(Gdk::GL::MODE_RGB |
				Gdk::GL::MODE_STENCIL | Gdk::GL::MODE_DEPTH);
		
		if(!glconfig)
			throw std::runtime_error("Could not get OpenGL to work.");
	}
	
	set_gl_capability(glconfig);
	
	m_lastFullPhoto = m_fullPhoto = -1;
	
	m_clickedElement = -1;
	m_mouseX = m_mouseY = m_clickX = m_clickY = 0;
	m_clickedTwice = m_clickedThreeTimes = false;
	
	m_dragMode = DRAG_NO;
	m_draggedElement = -1;
	
	m_leftCtrl = m_rightCtrl = m_leftShift = m_rightShift = false;
	
	add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |
		Gdk::BUTTON_RELEASE_MASK | Gdk::KEY_PRESS_MASK | Gdk::SCROLL_MASK);
	
	signal_scroll_event().connect(sigc::mem_fun(*this, &GLArea::on_scroll));
	signal_motion_notify_event().connect(sigc::mem_fun(*this, &GLArea::on_motion));
	signal_key_press_event().connect(sigc::mem_fun(*this, &GLArea::on_key));
	signal_key_release_event().connect(sigc::mem_fun(*this, &GLArea::on_key));
	signal_button_press_event().connect(sigc::mem_fun(*this, &GLArea::on_button_press));
	signal_button_release_event().connect(sigc::mem_fun(*this, &GLArea::on_button_release));
}

GLArea::~GLArea() {
	
}

bool GLArea::on_key(GdkEventKey* event) {
	/*std::cout << gdk_keyval_name(event->keyval);
	if(event->type == GDK_KEY_PRESS) std::cout << " pressed";
	else std::cout << " released";
	std::cout << std::endl;*/
	
	bool state;
	
	if(event->type == GDK_KEY_PRESS) state = true;
	else if(event->type == GDK_KEY_RELEASE) state = false;
	else return true; // should never happen
	
	switch(event->keyval) {
		case GDK_Control_L:
			m_leftCtrl = state;
			break;
		case GDK_Control_R:
			m_rightCtrl = state;
			break;
		case GDK_Shift_L:
			m_leftShift = state;
			break;
		case GDK_Shift_R:
			m_rightShift = state;
			break;
		case GDK_Up:
			if(state)
				setZoom(getZoom() + 10.0f);
			break;
		case GDK_Down:
			if(state)
				setZoom(getZoom() - 10.0f);
			break;
		case GDK_Left:
			if(state)
				setElement(getElement() - 1);
			break;
		case GDK_Right:
			if(state)
				setElement(getElement() + 1);
			break;
		case GDK_Return:
			if(state && !m_lib->inCollection())
				loadCollection(m_lib->getElement());
			break;
		case GDK_Escape:
			if(state) {
				if(m_lib->inCollection()) {
					loadCollection(-1);
					
					if(!m_timer.connected())
						m_timer = Glib::signal_timeout().connect(mem_fun(*this, &GLArea::timer), TIMER_INTERVAL, TIMER_PRIORITY);
				}
			}
			break;
		default:
			break;
	}
	
	return true;
}

bool GLArea::on_scroll(GdkEventScroll* event) {
	if(m_leftCtrl || m_rightCtrl) {
		if(event->direction == GDK_SCROLL_UP)
			setElement(getElement() - 1);
		else if(event->direction == GDK_SCROLL_DOWN)
			setElement(getElement() + 1);
	} else {
		if(event->direction == GDK_SCROLL_UP)
			setZoom(getZoom() + 10.0f);
		else if(event->direction == GDK_SCROLL_DOWN)
			setZoom(getZoom() - 10.0f);
	}
	
	return true;
}

bool GLArea::on_button_press(GdkEventButton* event) {
	grab_focus();
	
	m_clickX = int(event->x);
	m_clickY = int(event->y);
	
	if(event->button == 1) {
		if(event->y >= get_height() - (get_height() / 15.0f + DOCK_OUTLINE)) {
			if(event->type == GDK_BUTTON_PRESS) {
				m_dragMode = DRAG_NO;
				//get_window()->set_cursor(
				m_draggedElement = -1;
				std::string action = m_dock.getAction(int(event->x));
				
				if(action == "go-previous") setElement(getElement() - 1);
				else if(action == "go-next") setElement(getElement() + 1);
				else if(action == "open") loadCollection(m_lib->getElement());
				else if(action == "library") loadCollection(-1);
				else if(action == "refresh") {
					if(m_lib->inCollection()) loadCollection(m_lib->getCollection());
					else loadCollectionThumbnails();
				} else if(action == "basket")
					m_draggedElement = m_dock.getBasketPhoto(int(event->x));
			}
		} else {
			switch(event->type) {
				case GDK_BUTTON_PRESS: {
					int element = select(int(event->x), int(event->y));
					
					if(element != -1) {
						if(m_clickedElement == element) {
							if(!m_clickedTwice) m_clickedTwice = true;
							else m_clickedThreeTimes = true;
						} else m_clickedTwice = m_clickedThreeTimes = false;
						
						if(m_lib->inCollection()) {
							
							if(m_leftCtrl || m_rightCtrl) {
								m_lib->getCollectionPhotos()[element].selected =
									!m_lib->getCollectionPhotos()[element].selected;
								
								if(!m_timer.connected())
									m_timer = Glib::signal_timeout().connect(mem_fun(*this, &GLArea::timer), TIMER_INTERVAL, TIMER_PRIORITY);
							}
						}
					} else m_clickedTwice = m_clickedThreeTimes = false;
					
					m_clickedElement = element;
					m_draggedElement = m_clickedElement;
					
					break; }
				case GDK_2BUTTON_PRESS: {
					if(!m_clickedTwice) break;
					
					if(m_lib->inCollection()) {
						if(getElement() == m_clickedElement) {
							m_visu->setZoom(100);
							
							if(!m_timer.connected())
								m_timer = Glib::signal_timeout().connect(mem_fun(*this, &GLArea::timer), TIMER_INTERVAL, TIMER_PRIORITY);
						}
					} else loadCollection(m_lib->getElement());
					
					break; }
				case GDK_3BUTTON_PRESS: {
					if(!m_clickedThreeTimes) break;
					
					if(m_lib->inCollection()) {
						m_visu->setZoom(100);
						
						if(!m_timer.connected())
							m_timer = Glib::signal_timeout().connect(mem_fun(*this, &GLArea::timer), TIMER_INTERVAL, TIMER_PRIORITY);
					}
					
					break; }
				default:
					break;
			}
		}
	} else if(event->button == 3) {
		if(event->type == GDK_BUTTON_PRESS) {
			/*! \todo Be sure to use pointed photo for action, and popup
			  the View menu if there is a selection and a non-selected photo
			  is pointed. Should we also limit popup menus to outside of the
			  dock? */
			m_clickedElement = select(int(event->x), int(event->y));
			
			if((m_clickedElement != -1) && (m_lib->inCollection()))
				m_popupMenuPhoto->popup(event->button, event->time);
			else
				m_popupMenuView->popup(event->button, event->time);
		}
	}
	
	return true;
}

bool GLArea::on_button_release(GdkEventButton* event) {
	if(event->button == 1) {
		if(event->y >= get_height() - (get_height() / 15.0f + DOCK_OUTLINE)) {
			if(event->type == GDK_BUTTON_RELEASE) {
				if(!m_lib->inCollection() || (m_dragMode != DRAG_PHOTO) || (m_fullPhoto != -1)) {
					m_dragMode = DRAG_NO;
					return true;
				}
				
				int last = m_lib->getBasket().size();
				if(last < MAX_BASKET) {
					if(m_lastFullPhoto != -1) {
						//! \todo use build_path instead of this mess
						std::string md5string = str2md5(std::string(m_lib->getCollectionName() + "/" + m_lib->getCollectionPhotos()[m_lastFullPhoto].filename).c_str());
						
						std::string pathThumbnailFile = Glib::build_filename(
							Glib::build_filename(
								Glib::build_filename(
									Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"),
								md5string.substr(0, 2)),
							md5string.substr(2) + ".png");
						
						Glib::RefPtr<Gdk::Pixbuf> pixPhotoThumbnail;
						try {
							pixPhotoThumbnail = Gdk::Pixbuf::create_from_file(pathThumbnailFile);
							
							if(!m_lib->getCollectionPhotos()[m_lastFullPhoto].hasTexture) {
								glGenTextures(1, &m_lib->getCollectionPhotos()[m_lastFullPhoto].texPhoto);
								m_lib->getCollectionPhotos()[m_lastFullPhoto].hasTexture = true;
							}
							
							glBindTexture(GL_TEXTURE_2D, m_lib->getCollectionPhotos()[m_lastFullPhoto].texPhoto);
							
							glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
							glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
							
							GLint texFormat;
							if(pixPhotoThumbnail->get_has_alpha()) texFormat = GL_RGBA;
							else texFormat = GL_RGB;
							
							glTexImage2D(GL_TEXTURE_2D, 0, texFormat, pixPhotoThumbnail->get_width(), pixPhotoThumbnail->get_height(), 0,
								texFormat, GL_UNSIGNED_BYTE, pixPhotoThumbnail->get_pixels());
						} catch(const Glib::Exception& ex) {
							
						}
						
						m_lastFullPhoto = -1;
					}
					
					if(m_leftCtrl || m_rightCtrl) copyToBasket(m_draggedElement);
					else moveToBasket(m_draggedElement);
					
					setElement(getElement());
					
					if(!m_timer.connected())
						m_timer = Glib::signal_timeout().connect(mem_fun(*this, &GLArea::timer), TIMER_INTERVAL, TIMER_PRIORITY);
				}
				
				m_dragMode = DRAG_NO;
				m_draggedElement = -1;
			}
		} else {
			if(event->type == GDK_BUTTON_RELEASE) {
				if(m_dragMode == DRAG_BASKET) {
					//! \todo here we need a way to handle not being in a collection
					if(m_leftCtrl || m_rightCtrl) copyFromBasket(m_draggedElement);
					else moveFromBasket(m_draggedElement);
					
					m_draggedElement = -1;
				} else if(m_dragMode == DRAG_NO) {
					if(m_clickedElement != -1) {
						if(m_lib->inCollection()) {
							if(!m_leftCtrl && !m_rightCtrl)
								setElement(m_clickedElement);
						} else setElement(m_clickedElement);
					}
				}
				
				m_dragMode = DRAG_NO;
				
				if(!m_timer.connected())
					m_timer = Glib::signal_timeout().connect(mem_fun(*this, &GLArea::timer), TIMER_INTERVAL, TIMER_PRIORITY);
			}
		}
	}
	
	return true;
}

bool GLArea::on_motion(GdkEventMotion* event) {
	m_mouseX = int(event->x);
	m_mouseY = int(event->y);
	
	if((event->state & GDK_BUTTON1_MASK) && (m_draggedElement != -1) && (m_dragMode == DRAG_NO))
			if((event->x < m_clickX - 5) || (event->x > m_clickX + 5)
			|| (event->y < m_clickY - 5) || (event->y > m_clickY + 5))
				if(m_clickY >= get_height() - (get_height() / 15.0f + DOCK_OUTLINE))
					m_dragMode = DRAG_BASKET;
				else if(m_lib->inCollection()) m_dragMode = DRAG_PHOTO;
	
	if(event->y >= get_height() - (get_height() / 15.0f + DOCK_OUTLINE))
			m_dock.raise(int(event->x), int(event->y));
	
	if(event->y < get_height() - (get_height() / 15.0f + DOCK_OUTLINE * 2))
			if(m_dragMode != DRAG_BASKET)
				m_dock.raise(false);
	
	if(!m_timer.connected())
		if(m_dock.update() || (m_dragMode != DRAG_NO))
			m_timer = Glib::signal_timeout().connect(mem_fun(*this, &GLArea::timer), TIMER_INTERVAL, TIMER_PRIORITY);
	
	return true;
}

bool GLArea::timer() {
	bool next = false;
	
	next = m_dock.update() ? true : next;
	if(m_lib->inCollection()) next = m_visu->updateScene() ? true : next;
	else next = m_lib->updateScene() ? true : next;
	next = (m_dragMode != DRAG_NO) ? true : next;
	
	redraw();
	return next;
}

int GLArea::select(int x, int y) {
	Glib::RefPtr<Gdk::GL::Window> glwindow = get_gl_window();
	
	if (!glwindow->gl_begin(get_gl_context()))
		return -1;
	
	GLint viewport[4];
	glGetIntegerv(GL_VIEWPORT, viewport);
	
	GLuint buffer[512];
	glSelectBuffer(512, buffer);
	
	glRenderMode(GL_SELECT);
	
	glInitNames();
	glPushName(0);
	
	// init picking region
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	
	gluPickMatrix(x, viewport[3] - y, 1.0f, 1.0f, viewport);
	gluPerspective(65, float(get_width()) / get_height(), 1, 160);
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	if(m_lib->inCollection()) {
		if(m_visu != NULL) m_visu->renderScene();
	} else m_lib->renderScene(/*get_width(), get_height()*/);
	
	glFlush();
	
	// restore projection matrix
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	
	glMatrixMode(GL_MODELVIEW);
	
	GLint hits = glRenderMode(GL_RENDER);
	glwindow->gl_end();
	
	if(hits > 0) {
		int nearestObject = buffer[3], objectDepth = buffer[1];
		
		for (int i = 1; i < hits; i++) {
			if(buffer[i* 4 + 1] < GLuint(objectDepth)) {
				nearestObject = buffer[i * 4 + 3];
				objectDepth = buffer[i * 4 + 1];
			}
		}
		
		return nearestObject - 1;
	}
	
	return -1;
}

void GLArea::setVisualisation(visualisation *visu) {
	m_visu = visu;
	if(is_realized()) {
		m_visu->setSelectedTexture(m_texSelected);
		if(m_lib->inCollection()) redraw();
	}
}

void GLArea::setPopupMenus(Gtk::Menu *popupMenuView, Gtk::Menu *popupMenuPhoto, Gtk::Menu *menuFrame, Gtk::RadioButtonGroup frameGroup) {
	m_popupMenuView = popupMenuView;
	m_popupMenuPhoto = popupMenuPhoto;
	m_menuFrame = menuFrame;
	m_frameGroup = frameGroup;
}

void GLArea::setFrame(int frame) {
	int element;
	if(m_clickedElement != -1) element = m_clickedElement;
	else element = getElement();
	
	if(m_lib->inCollection()) {
		if(frame == -1) {
			m_lib->getCollectionPhotos()[element].hasFrame = false;
			m_lib->getCollectionPhotos()[element].texFrame = 0;
		} else {
			m_lib->getCollectionPhotos()[element].hasFrame = true;
			m_lib->getCollectionPhotos()[element].texFrame = m_texFrames[frame];
		}
		
		if(!m_timer.connected())
			m_timer = Glib::signal_timeout().connect(mem_fun(*this, &GLArea::timer), TIMER_INTERVAL, TIMER_PRIORITY);
	}
	
	m_clickedElement = -1;
}

void GLArea::showFrames(bool state) {
	if(m_visu != NULL) {
		m_visu->showFrames(state);
	
		if(m_lib->inCollection())
			if(!m_timer.connected())
				m_timer = Glib::signal_timeout().connect(mem_fun(*this, &GLArea::timer), TIMER_INTERVAL, TIMER_PRIORITY);
	}
}

void GLArea::loadCollectionThumbnails() {
	int t = 0;
	
	std::string md5string;
	
	for(unsigned int i = 0; i < m_lib->getCollections().size(); i++) {
		if(m_lib->getCollections()[i].getPhotos().size()) {
			md5string = str2md5(std::string(m_lib->getCollections()[i].getName() + "/" + m_lib->getCollections()[i].getPhotos()[0].filename).c_str());
			
			std::string pathThumbnailFile = Glib::build_filename(
				Glib::build_filename(
					Glib::build_filename(
						Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"),
					md5string.substr(0, 2)),
				md5string.substr(2) + ".png");
			if(Glib::file_test(pathThumbnailFile, Glib::FILE_TEST_EXISTS)) {
				Glib::RefPtr<Gdk::Pixbuf> pixbuf;
				try {
					pixbuf = Gdk::Pixbuf::create_from_file(pathThumbnailFile);
				} catch(const Glib::Exception& ex) {
					continue;
				}
				
				glBindTexture(GL_TEXTURE_2D, m_texCollectionThumbnails[t]);
				
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
				
				GLint texFormat;
				if(pixbuf->get_has_alpha()) texFormat = GL_RGBA;
				else texFormat = GL_RGB;
				
				glTexImage2D(GL_TEXTURE_2D, 0, texFormat, pixbuf->get_width(), pixbuf->get_height(), 0,
					texFormat, GL_UNSIGNED_BYTE, pixbuf->get_pixels());
				
				m_lib->getCollections()[i].setThumbnailTexture(m_texCollectionThumbnails[t]);
				t++;
			}
		}
	}
}

void GLArea::loadCollection(int collection) {
	// unload current collection
	if(m_lib->inCollection())
		for(unsigned int i = 0; i < m_lib->getCollectionPhotos().size(); i++)
			if(m_lib->getCollectionPhotos()[i].hasTexture) {
				glDeleteTextures(1, &m_lib->getCollectionPhotos()[i].texPhoto);
				m_lib->getCollectionPhotos()[i].hasTexture = false;
				m_lib->getCollectionPhotos()[i].texPhoto = 0;
			}
	
	m_dragMode = DRAG_NO;
	m_draggedElement = -1;
	m_lastFullPhoto = m_fullPhoto = -1;
	
	m_lib->setCollection(collection);
	
	// update dock
	m_dock.clearLocation();
	m_dock.pushLocation("Library");
	if(m_lib->inCollection()) {
		m_dock.pushLocation(m_lib->getCollectionName());
		
		m_dock.getIcon("open")->visible = false;
		m_dock.getIcon("library")->visible = true;
		m_dock.getIcon("slideshow")->visible = true;
	} else {
		m_dock.getIcon("open")->visible = true;
		m_dock.getIcon("library")->visible = false;
		m_dock.getIcon("slideshow")->visible = false;
		
		return;
	}
	
	// load new collection
	std::string md5string;
	
	for(unsigned int i = 0; i < m_lib->getCollectionPhotos().size(); i++) {
		md5string = str2md5(std::string(m_lib->getCollectionName() + "/" + m_lib->getCollectionPhotos()[i].filename).c_str());
		
		std::string pathThumbnailFile = Glib::build_filename(
			Glib::build_filename(
				Glib::build_filename(
					Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"),
				md5string.substr(0, 2)),
			md5string.substr(2) + ".png");
						
		Glib::RefPtr<Gdk::Pixbuf> pixPhotoThumbnail;
		try {
			pixPhotoThumbnail = Gdk::Pixbuf::create_from_file(pathThumbnailFile);
		} catch(const Glib::Exception& ex) {
			continue;
		}
		
		if(!m_lib->getCollectionPhotos()[i].hasTexture) {
			glGenTextures(1, &m_lib->getCollectionPhotos()[i].texPhoto);
			m_lib->getCollectionPhotos()[i].hasTexture = true;
		}
		glBindTexture(GL_TEXTURE_2D, m_lib->getCollectionPhotos()[i].texPhoto);
		
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		
		GLint texFormat;
		if(pixPhotoThumbnail->get_has_alpha()) texFormat = GL_RGBA;
		else texFormat = GL_RGB;
		
		glTexImage2D(GL_TEXTURE_2D, 0, texFormat, pixPhotoThumbnail->get_width(), pixPhotoThumbnail->get_height(), 0,
			texFormat, GL_UNSIGNED_BYTE, pixPhotoThumbnail->get_pixels());
	}
	
	setElement(0);
}

int GLArea::getElement() {
	if(!m_lib->inCollection())
		return m_lib->getElement();
	
	if(m_visu != NULL)
		return m_visu->getPhoto();
	
	return 0;
}

void GLArea::setElement(int element) {
	if(!m_lib->inCollection()) {
		m_lib->setElement(element);
	} else if(m_lib->getCollectionPhotos().size()) {
		if(m_visu != NULL) m_visu->moveTo(element);
		element = m_visu->getPhoto();
		
		if((m_fullPhoto == -1) && (m_lastFullPhoto != element)) {
			// revert last full photo to thumbnail
			if(m_lastFullPhoto != -1) {
				std::string md5string = str2md5(std::string(m_lib->getCollectionName() + "/" + m_lib->getCollectionPhotos()[m_lastFullPhoto].filename).c_str());
				
				std::string pathThumbnailFile = Glib::build_filename(
					Glib::build_filename(
						Glib::build_filename(
							Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"),
						md5string.substr(0, 2)),
					md5string.substr(2) + ".png");
								
				Glib::RefPtr<Gdk::Pixbuf> pixPhotoThumbnail;
				try {
					pixPhotoThumbnail = Gdk::Pixbuf::create_from_file(pathThumbnailFile);
					
					if(!m_lib->getCollectionPhotos()[m_lastFullPhoto].hasTexture) {
						glGenTextures(1, &m_lib->getCollectionPhotos()[m_lastFullPhoto].texPhoto);
						m_lib->getCollectionPhotos()[m_lastFullPhoto].hasTexture = true;
					}
					
					glBindTexture(GL_TEXTURE_2D, m_lib->getCollectionPhotos()[m_lastFullPhoto].texPhoto);
					
					glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
					glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
					
					GLint texFormat;
					if(pixPhotoThumbnail->get_has_alpha()) texFormat = GL_RGBA;
					else texFormat = GL_RGB;

					glTexImage2D(GL_TEXTURE_2D, 0, texFormat, pixPhotoThumbnail->get_width(), pixPhotoThumbnail->get_height(), 0,
						texFormat, GL_UNSIGNED_BYTE, pixPhotoThumbnail->get_pixels());
				} catch(const Glib::Exception& ex) {
					
				}
				
				m_lastFullPhoto = -1;
			}
			
			// load new full photo
			m_fullPhoto = element;
			Glib::Thread::create(sigc::bind(sigc::mem_fun(*this, &GLArea::loadFullPhoto), Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "library"), m_lib->getCollectionName()), m_lib->getCollectionPhotos()[element].filename)), false);
		}
	} else return;

	if(!m_timer.connected())
		m_timer = Glib::signal_timeout().connect(mem_fun(*this, &GLArea::timer), TIMER_INTERVAL, TIMER_PRIORITY);
}

void GLArea::loadFullPhoto(std::string filename) {
	try {
		m_pixFullPhoto = Gdk::Pixbuf::create_from_file(filename);
	} catch(const Glib::Exception& ex) {
		m_fullPhoto = -1;
		throw Glib::Thread::Exit();
	}
	
	m_dispPhotoLoaded.emit();
}

void GLArea::on_fullPhotoLoaded() {
	if(!m_lib->inCollection()) {
		// the user left the collection while the photo was loading
		m_pixFullPhoto.clear();
		return;
	}
	
	if(m_fullPhoto == m_visu->getPhoto()) {
		glBindTexture(GL_TEXTURE_2D, m_lib->getCollectionPhotos()[m_fullPhoto].texPhoto);
		
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		
		GLint texFormat;
		if(m_pixFullPhoto->get_has_alpha()) texFormat = GL_RGBA;
		else texFormat = GL_RGB;

		glTexImage2D(GL_TEXTURE_2D, 0, texFormat, m_pixFullPhoto->get_width(), m_pixFullPhoto->get_height(), 0,
			texFormat, GL_UNSIGNED_BYTE, m_pixFullPhoto->get_pixels());
		
		m_pixFullPhoto.clear();
		m_lastFullPhoto = m_fullPhoto;
		m_fullPhoto = -1;

		if(!m_timer.connected())
			m_timer = Glib::signal_timeout().connect(mem_fun(*this, &GLArea::timer), TIMER_INTERVAL, TIMER_PRIORITY);
	} else {
		m_fullPhoto = m_visu->getPhoto();
		Glib::Thread::create(sigc::bind(sigc::mem_fun(*this, &GLArea::loadFullPhoto), Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "library"), m_lib->getCollectionName()), m_lib->getCollectionPhotos()[m_fullPhoto].filename)), false);
	}
}

void GLArea::moveToBasket(int photo) {
	if(photo == -1) photo = m_clickedElement;
	if(photo < 0) return;
	
	std::string pathCollectionFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "library"), m_lib->getCollectionName()), m_lib->getCollectionPhotos()[photo].filename);
	std::string pathBasketFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "library"), m_lib->getCollectionPhotos()[photo].filename);
	g_rename(pathCollectionFile.c_str(), pathBasketFile.c_str());
	
	std::string md5string = str2md5(std::string(m_lib->getCollectionName() + "/" + m_lib->getCollectionPhotos()[photo].filename).c_str());
	std::string newMd5string = str2md5(m_lib->getCollectionPhotos()[photo].filename.c_str());
	std::string pathThumbnailFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"), md5string.substr(0, 2)), md5string.substr(2) + ".png");
	std::string pathNewThumbnail = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"), newMd5string.substr(0, 2));
	std::string pathNewThumbnailFile = Glib::build_filename(pathNewThumbnail, newMd5string.substr(2) + ".png");
	
	g_mkdir(pathNewThumbnail.c_str(), 0700);
	g_rename(pathThumbnailFile.c_str(), pathNewThumbnailFile.c_str());
	
	m_lib->getBasket().push_back(m_lib->getCollectionPhotos()[photo]);
	m_lib->getCollectionPhotos().erase(m_lib->getCollectionPhotos().begin() + photo);
}

void GLArea::copyToBasket(int photo) {
	//! \todo buggy, we need to have our own texture
	if(photo == -1) photo = m_clickedElement;
	if(photo < 0) return;
	
	gchar *fileContents;
	gsize fileLength;
	
	std::string pathCollectionFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "library"), m_lib->getCollectionName()), m_lib->getCollectionPhotos()[photo].filename);
	std::string pathBasketFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "library"), m_lib->getCollectionPhotos()[photo].filename);
	
	if(g_file_get_contents(pathCollectionFile.c_str(), &fileContents, &fileLength, NULL)) {
		g_file_set_contents(pathBasketFile.c_str(), fileContents, fileLength, NULL);
		g_free(fileContents);
	}
	
	std::string md5string = str2md5(std::string(m_lib->getCollectionName() + "/" + m_lib->getCollectionPhotos()[photo].filename).c_str());
	std::string newMd5string = str2md5(m_lib->getCollectionPhotos()[photo].filename.c_str());
	std::string pathThumbnailFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"), md5string.substr(0, 2)), md5string.substr(2) + ".png");
	std::string pathNewThumbnail = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"), newMd5string.substr(0, 2));
	std::string pathNewThumbnailFile = Glib::build_filename(pathNewThumbnail, newMd5string.substr(2) + ".png");
	
	g_mkdir(pathNewThumbnail.c_str(), 0700);
	if(g_file_get_contents(pathThumbnailFile.c_str(), &fileContents, &fileLength, NULL)) {
		g_file_set_contents(pathNewThumbnailFile.c_str(), fileContents, fileLength, NULL);
		g_free(fileContents);
	}
	
	m_lib->getBasket().push_back(m_lib->getCollectionPhotos()[photo]);
}

void GLArea::moveFromBasket(int basketPhoto) {
	if(!m_lib->inCollection()) return; //! \todo temporary
	
	std::string pathBasketFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "library"), m_lib->getBasket()[basketPhoto].filename);
	std::string pathCollectionFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "library"), m_lib->getCollectionName()), m_lib->getBasket()[basketPhoto].filename);
	g_rename(pathBasketFile.c_str(), pathCollectionFile.c_str());
	
	std::string md5string = str2md5(m_lib->getBasket()[basketPhoto].filename.c_str());
	std::string newMd5string = str2md5(std::string(m_lib->getCollectionName() + "/" + m_lib->getBasket()[basketPhoto].filename).c_str());
	std::string pathThumbnailFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"), md5string.substr(0, 2)), md5string.substr(2) + ".png");
	std::string pathNewThumbnail = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"), newMd5string.substr(0, 2));
	std::string pathNewThumbnailFile = Glib::build_filename(pathNewThumbnail, newMd5string.substr(2) + ".png");
	
	g_mkdir(pathNewThumbnail.c_str(), 0700);
	g_rename(pathThumbnailFile.c_str(), pathNewThumbnailFile.c_str());
	
	m_lib->getCollectionPhotos().push_back(m_lib->getBasket()[basketPhoto]);
	m_lib->getBasket().erase(m_lib->getBasket().begin() + basketPhoto);
}

void GLArea::copyFromBasket(int basketPhoto) {
	if(!m_lib->inCollection()) return; //! \todo temporary
	
	gchar *fileContents;
	gsize fileLength;

	std::string pathBasketFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "library"), m_lib->getBasket()[basketPhoto].filename);
	std::string pathCollectionFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "library"), m_lib->getCollectionName()), m_lib->getBasket()[basketPhoto].filename);
	
	if(g_file_get_contents(pathBasketFile.c_str(), &fileContents, &fileLength, NULL)) {
		g_file_set_contents(pathCollectionFile.c_str(), fileContents, fileLength, NULL);
		g_free(fileContents);
	}
	
	std::string md5string = str2md5(m_lib->getBasket()[basketPhoto].filename.c_str());
	std::string newMd5string = str2md5(std::string(m_lib->getCollectionName() + "/" + m_lib->getBasket()[basketPhoto].filename).c_str());
	std::string pathThumbnailFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"), md5string.substr(0, 2)), md5string.substr(2) + ".png");
	std::string pathNewThumbnail = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"), newMd5string.substr(0, 2));
	std::string pathNewThumbnailFile = Glib::build_filename(pathNewThumbnail, newMd5string.substr(2) + ".png");
	
	g_mkdir(pathNewThumbnail.c_str(), 0700);
	if(g_file_get_contents(pathThumbnailFile.c_str(), &fileContents, &fileLength, NULL)) {
		g_file_set_contents(pathNewThumbnailFile.c_str(), fileContents, fileLength, NULL);
		g_free(fileContents);
	}
	
	m_lib->getCollectionPhotos().push_back(m_lib->getBasket()[basketPhoto]);
}

void GLArea::deletePhoto(int photo) {
	if(!m_lib->inCollection()) return;
	if(photo == -1) photo = m_clickedElement;
	if(photo < 0) return;
	
	std::string pathCollectionFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "library"), m_lib->getCollectionName()), m_lib->getCollectionPhotos()[photo].filename);
	g_unlink(pathCollectionFile.c_str());
	
	std::string md5string = str2md5(std::string(m_lib->getCollectionName() + "/" + m_lib->getCollectionPhotos()[photo].filename).c_str());
	std::string newMd5string = str2md5(m_lib->getCollectionPhotos()[photo].filename.c_str());
	std::string pathThumbnailFile = Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"), md5string.substr(0, 2)), md5string.substr(2) + ".png");
	g_unlink(pathThumbnailFile.c_str());
	
	m_lib->getCollectionPhotos().erase(m_lib->getCollectionPhotos().begin() + photo);
}

void GLArea::setZoom(float zoom) {
	if(zoom < 0) zoom = 0;
	else if(zoom > 100) zoom = 100;
	
	if(m_visu != NULL) {
		m_visu->setZoom(zoom);
		
		if(m_lib->inCollection())
			if(!m_timer.connected())
				m_timer = Glib::signal_timeout().connect(mem_fun(*this, &GLArea::timer), TIMER_INTERVAL, TIMER_PRIORITY);
	}
}

float GLArea::getZoom() {
	if(m_visu != NULL)
		return m_visu->getZoom();
	
	return 0;
}

bool GLArea::redraw() {
	
	Glib::RefPtr<Gdk::GL::Window> glwindow = get_gl_window();
	
	if(!glwindow->gl_begin(get_gl_context()))
		return false;
	
	{
		GLenum error;
		while((error = glGetError()) != GL_NO_ERROR)
			std::cout << "GL: error " << error << ": " << gluErrorString(glGetError()) << std::endl;
	}
	
	// clear all
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	glEnable(GL_DEPTH_TEST);
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	
	gluPerspective(65, float(get_width()) / get_height(), 1, 160);
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	if(m_lib->inCollection()) {
		if(m_visu != NULL) m_visu->renderScene(/*get_width(), get_height()*/);
	} else m_lib->renderScene(/*get_width(), get_height()*/);
	
	// 2d Overlay
	glDisable(GL_COLOR_MATERIAL);
	glDisable(GL_LIGHTING);
	glDisable(GL_DEPTH_TEST);
	glClear(GL_DEPTH_BUFFER_BIT);
	glColor3f(1.0, 1.0, 1.0);
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	
	glOrtho(0, get_width(), 0, get_height(), -1, 1);
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	/*glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER, 0.1f);*/
	
	glEnable(GL_BLEND);
	
	if(m_lib->inCollection()) { }
	else m_lib->renderOverlay(get_width(), get_height());
	// dock
	glEnable(GL_TEXTURE_2D);
	m_dock.render();
	
	if(m_dragMode != DRAG_NO) {
		glBindTexture(GL_TEXTURE_2D, m_texIcons[0]);
		glBegin(GL_QUADS);
			glTexCoord2i(0, 1);
			glVertex2i(m_mouseX - get_height() / 30, get_height() - m_mouseY - get_height() / 30);
			glTexCoord2i(1, 1);
			glVertex2i(m_mouseX + get_height() / 30, get_height() - m_mouseY - get_height() / 30);
			glTexCoord2i(1, 0);
			glVertex2i(m_mouseX + get_height() / 30, get_height() - m_mouseY + get_height() / 30);
			glTexCoord2i(0, 0);
			glVertex2i(m_mouseX - get_height() / 30, get_height() - m_mouseY + get_height() / 30);
		glEnd();
	}
	glDisable(GL_TEXTURE_2D);
	
	glDisable(GL_BLEND);
	
	
	if(glwindow->is_double_buffered())
		glwindow->swap_buffers();
	else
		glFlush();
	
	glwindow->gl_end();
	
	return true;
}

void GLArea::on_realize() {
	Gtk::DrawingArea::on_realize();
	grab_focus();
	
	Glib::RefPtr<Gdk::GL::Window> glwindow = get_gl_window();
	
	if (!glwindow->gl_begin(get_gl_context()))
		return;
	
	// error texture
	{
		GLuint texError;
		glGenTextures(1, &texError);
		
		Glib::RefPtr<Gdk::Pixbuf> pixError;
		pixError = Glib::wrap(rsvg_pixbuf_from_file_at_zoom("../share/photoflip/gui/error.svg", 5, 5, NULL), true);
		
		glBindTexture(GL_TEXTURE_2D, texError);
		
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pixError->get_width(), pixError->get_height(), 0,
			GL_RGBA, GL_UNSIGNED_BYTE, pixError->get_pixels());
		
		m_lib->setErrorTexture(texError);
	}
	
	// dock icons
	glGenTextures(MAX_ICONS, m_texIcons);

	{
		Glib::RefPtr<Gdk::Pixbuf> pixPhoto;
		pixPhoto = Glib::wrap(rsvg_pixbuf_from_file_at_zoom("../share/photoflip/gui/photo.svg", 5, 5, NULL), true);
		
		glBindTexture(GL_TEXTURE_2D, m_texIcons[0]);
		
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pixPhoto->get_width(), pixPhoto->get_height(), 0,
			GL_RGBA, GL_UNSIGNED_BYTE, pixPhoto->get_pixels());
	}
	
	{
		Glib::RefPtr<Gdk::Pixbuf> pixGoPrevious;
		pixGoPrevious = Glib::wrap(rsvg_pixbuf_from_file_at_zoom("../share/photoflip/gui/dock/go-previous.svg", 5, 5, NULL), true);
		
		glBindTexture(GL_TEXTURE_2D, m_texIcons[1]);
		
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pixGoPrevious->get_width(), pixGoPrevious->get_height(), 0,
			GL_RGBA, GL_UNSIGNED_BYTE, pixGoPrevious->get_pixels());
	}
	
	{
		Glib::RefPtr<Gdk::Pixbuf> pixGoNext;
		pixGoNext = Glib::wrap(rsvg_pixbuf_from_file_at_zoom("../share/photoflip/gui/dock/go-next.svg", 5, 5, NULL), true);
		
		glBindTexture(GL_TEXTURE_2D, m_texIcons[2]);
		
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pixGoNext->get_width(), pixGoNext->get_height(), 0,
			GL_RGBA, GL_UNSIGNED_BYTE, pixGoNext->get_pixels());
	}
	
	{
		Glib::RefPtr<Gdk::Pixbuf> pixOpen;
		pixOpen = Glib::wrap(rsvg_pixbuf_from_file_at_zoom("../share/photoflip/gui/dock/open.svg", 5, 5, NULL), true);
		
		glBindTexture(GL_TEXTURE_2D, m_texIcons[3]);
		
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pixOpen->get_width(), pixOpen->get_height(), 0,
			GL_RGBA, GL_UNSIGNED_BYTE, pixOpen->get_pixels());
	}
	
	{
		Glib::RefPtr<Gdk::Pixbuf> pixLibrary;
		pixLibrary = Glib::wrap(rsvg_pixbuf_from_file_at_zoom("../share/photoflip/gui/dock/library.svg", 5, 5, NULL), true);
		
		glBindTexture(GL_TEXTURE_2D, m_texIcons[4]);
		
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pixLibrary->get_width(), pixLibrary->get_height(), 0,
			GL_RGBA, GL_UNSIGNED_BYTE, pixLibrary->get_pixels());
	}
	
	{
		Glib::RefPtr<Gdk::Pixbuf> pixRefresh;
		pixRefresh = Glib::wrap(rsvg_pixbuf_from_file_at_zoom("../share/photoflip/gui/dock/refresh.svg", 5, 5, NULL), true);
		
		glBindTexture(GL_TEXTURE_2D, m_texIcons[5]);
		
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pixRefresh->get_width(), pixRefresh->get_height(), 0,
			GL_RGBA, GL_UNSIGNED_BYTE, pixRefresh->get_pixels());
	}
	
	{
		Glib::RefPtr<Gdk::Pixbuf> pixRefresh;
		pixRefresh = Glib::wrap(rsvg_pixbuf_from_file_at_zoom("../share/photoflip/gui/dock/slideshow.svg", 5, 5, NULL), true);
		
		glBindTexture(GL_TEXTURE_2D, m_texIcons[6]);
		
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pixRefresh->get_width(), pixRefresh->get_height(), 0,
			GL_RGBA, GL_UNSIGNED_BYTE, pixRefresh->get_pixels());
	}
	
	// set up dock
	m_dock.initTexture();
	m_dock.setSize(get_width(), get_height() / 15);
	m_dock.packIcon("go-previous", m_texIcons[1]);
	m_dock.packIcon("go-next", m_texIcons[2]);
	m_dock.packIcon("open", m_texIcons[3]);
	m_dock.packIcon("library", m_texIcons[4], false);
	m_dock.packIcon("refresh", m_texIcons[5]);
	m_dock.packIcon("slideshow", m_texIcons[6], false);
	
	m_dock.pushLocation("Library");
	
	// load photo frames
	glGenTextures(MAX_FRAMES, m_texFrames);
	
	std::string pathFrames = "../share/photoflip/frames";
	Glib::Dir dirFrames(pathFrames);
	std::list<std::string> entries(dirFrames.begin(), dirFrames.end());
	
	int f = 0;
	for(std::list<std::string>::const_iterator itFrame = entries.begin(); itFrame!= entries.end(); ++itFrame) {
		if((*itFrame).find(".") == 0) continue;
		if(f == MAX_FRAMES) std::cerr << "MAX_FRAMES reached, expect things to get bad!" << std::endl;
		
		std::string pathFrame = Glib::build_filename(pathFrames, *itFrame);
		if(!Glib::file_test(pathFrame, Glib::FILE_TEST_IS_DIR)) {
			Glib::RefPtr<Gdk::Pixbuf> pixFrame;
			pixFrame = Glib::wrap(rsvg_pixbuf_from_file_at_zoom(pathFrame.c_str(), 2, 2, NULL), true);
			
			if(pixFrame) {
				// create texture
				glBindTexture(GL_TEXTURE_2D, m_texFrames[f]);
				
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
				glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
				
				glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pixFrame->get_width(), pixFrame->get_height(), 0,
					GL_RGBA, GL_UNSIGNED_BYTE, pixFrame->get_pixels());
				
				// add frame to menu
				Gtk::RadioMenuItem *radio = Gtk::manage(new Gtk::RadioMenuItem(m_frameGroup, *itFrame));
				radio->show();
				radio->signal_activate().connect(sigc::bind(sigc::mem_fun(*this, &GLArea::setFrame), f));
				m_menuFrame->append(*radio);
				
				f++;
			}
		}
	}
	
	dirFrames.close();
	
	// load selection mark
	glGenTextures(1, &m_texSelected);
	
	{
		Glib::RefPtr<Gdk::Pixbuf> pixSelected;
		pixSelected = Glib::wrap(rsvg_pixbuf_from_file_at_zoom("../share/photoflip/gui/selected.svg", 2, 2, NULL), true);
		
		glBindTexture(GL_TEXTURE_2D, m_texSelected);
		
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pixSelected->get_width(), pixSelected->get_height(), 0,
			GL_RGBA, GL_UNSIGNED_BYTE, pixSelected->get_pixels());
	}
	
	if(m_visu != NULL)
		m_visu->setSelectedTexture(m_texSelected);
	
	// load thumbnails for basket
	for(unsigned int i = 0; i < m_lib->getBasket().size(); i++) {
		std::string md5string = str2md5(std::string(m_lib->getBasket()[i].filename).c_str());
		
		std::string pathThumbnailFile = Glib::build_filename(
			Glib::build_filename(
				Glib::build_filename(
					Glib::build_filename(Glib::get_home_dir(), ".photoflip"), "thumbnails"),
				md5string.substr(0, 2)),
			md5string.substr(2) + ".png");
		
		Glib::RefPtr<Gdk::Pixbuf> pixPhotoThumbnail;
		try {
			pixPhotoThumbnail = Gdk::Pixbuf::create_from_file(pathThumbnailFile);
			
			glGenTextures(1, &m_lib->getBasket()[i].texPhoto);
			m_lib->getBasket()[i].hasTexture = true;
			
			glBindTexture(GL_TEXTURE_2D, m_lib->getBasket()[i].texPhoto);
			
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
			
			GLint texFormat;
			if(pixPhotoThumbnail->get_has_alpha()) texFormat = GL_RGBA;
			else texFormat = GL_RGB;
			
			glTexImage2D(GL_TEXTURE_2D, 0, texFormat, pixPhotoThumbnail->get_width(), pixPhotoThumbnail->get_height(), 0,
				texFormat, GL_UNSIGNED_BYTE, pixPhotoThumbnail->get_pixels());
		} catch(const Glib::Exception& ex) {
			
		}
	}
	
	// load a thumbnail for each collection
	glGenTextures(MAX_COLLECTIONS, m_texCollectionThumbnails);
	loadCollectionThumbnails();
	
	// init library visualisation
	m_lib->setSize(get_width(), get_height());
	
	// init full photo loaded dispatcher
	m_dispPhotoLoaded.connect(mem_fun(*this, &GLArea::on_fullPhotoLoaded));
	
	// init viewport
	glViewport(0, 0, get_width(), get_height());
	
	glEnable(GL_DEPTH_TEST);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	
	m_lib->initScene();
	
	if(!m_timer.connected())
		m_timer = Glib::signal_timeout().connect(mem_fun(*this, &GLArea::timer), TIMER_INTERVAL, TIMER_PRIORITY);	
	
	glwindow->gl_end();
	
	// start thumbnail generation
	Glib::Thread::create(sigc::mem_fun(m_lib, &library::createThumbnails), false);
}

bool GLArea::on_configure_event(GdkEventConfigure* event) {
	Glib::RefPtr<Gdk::GL::Window> glwindow = get_gl_window();
	
	if (!glwindow->gl_begin(get_gl_context()))
		return false;
	
	glViewport(0, 0, get_width(), get_height());
	
	m_lib->setSize(get_width(), get_height());
	m_dock.setSize(get_width(), get_height() / 15);
	
	glwindow->gl_end();
	
	return true;
}

bool GLArea::on_expose_event(GdkEventExpose *event) {
	return redraw();
}
